/* Copyright 2009 Anthony Surma
This file is part of Java's Kademlia Extension (JKADX).

JKADX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.

JKADX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JKADX.  If not, see <http://www.gnu.org/licenses/>.
 */

package jkadx.dht;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;

/**
 *
 * @author Anthony Surma
 */
public class TestKBucket implements DHTInterface
{
    private DHT test = new DHT( this, new UniqueId());
    private final static byte[] IP_ADDRESS1 = { 123, 123, 123, 123 };
    private final static byte[] IP_ADDRESS2 = { 123, 123, 123, 122 };

    @Test
    public void testToString()
    {
	int K = UniqueId.NBITS;
	KBucket kbucket = new KBucket(0);
	UniqueId UniqueId1 = new UniqueId();
	UniqueId UniqueId2 = new UniqueId("1");
	Contact contact1;
	Contact contact2;
	String bits1 = "";
	String bits2 = "";
	String expectedresults;
	String obtainedresults;

	obtainedresults = kbucket.toString();
	expectedresults = "";
	assertEquals("Failed to get proper toString value #1.", expectedresults, obtainedresults);

	contact1 = new Contact(UniqueId1, IP_ADDRESS1, 1234);
	contact2 = new Contact(UniqueId2, IP_ADDRESS2, 2234);
	kbucket.updateIfEqualMatchElseAddIfNotFull(contact1);
	kbucket.updateIfEqualMatchElseAddIfNotFull(contact2);
	obtainedresults = kbucket.toString();
	for (int i = 0; i < K; ++i)
	{
	    bits1 += 0;
	}
	for (int i = 0; i < K - 1; ++i)
	{
	    bits2 += 0;
	}
	bits2 = bits2 + "1";
	expectedresults = bits1 + "/123.123.123.123/1234" + KBucket.SEPERATOR + bits2 + "/123.123.123.122/2234" + KBucket.SEPERATOR;
	assertEquals("Failed to get proper toString value #2.", expectedresults, obtainedresults);
    }

    @Test 
    public void testUpdateIfKnownOrAddIfNotFull()
    {
	final int num = DHT.K * 2;
	KBucket kbucket = new KBucket(0);
	Contact firstcontact;
	Contact secondcontact;
	Contact testcontact;
	Contact returnedcontact;
	int expectedint;
	int obtainedint;
        UniqueId randomUniqueId;

        expectedint = 0;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to return 0 when empty", expectedint, obtainedint);      

	firstcontact = new Contact(UniqueId.newRandom(), IP_ADDRESS1, 0 );
	returnedcontact = kbucket.updateIfEqualMatchElseAddIfNotFull(firstcontact);
	assertEquals("Failed to return null when adding contact to empty kbucket", null, returnedcontact);       

        expectedint = 1;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to add when not full", expectedint, obtainedint);

	secondcontact = new Contact(UniqueId.newRandom(), IP_ADDRESS2, 1 );
	returnedcontact = kbucket.updateIfEqualMatchElseAddIfNotFull(secondcontact);
	assertEquals("Failed to return null whening add unknown contact to non-full kbucket", null, returnedcontact);
        
	expectedint = 2;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to add when not full", expectedint, obtainedint);
	
	testcontact = new Contact(secondcontact.uniqueId, IP_ADDRESS2, 3);
	returnedcontact = kbucket.updateIfEqualMatchElseAddIfNotFull(testcontact);
       
        expectedint = 2;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to not add matching contact", expectedint, obtainedint);
        
        assertFalse("Failed to return not null when adding equal but not matching contact", (returnedcontact == null));
	
	assertEquals("Failed to return the existing contact when adding equal but not matching contact", secondcontact, returnedcontact);
        assertTrue("Failed to return the existing contact when adding equal but not matching contact", secondcontact.matchesSocketAddress(returnedcontact));
        
        assertEquals("One contact should be locked here.", kbucket.getNumContactsLocked(), 1);

        returnedcontact.isLocked.set(false);

        assertEquals("No contacts should be locked here.", kbucket.getNumContactsLocked(), 0);

	testcontact = new Contact(secondcontact.uniqueId, IP_ADDRESS2, 1);
	returnedcontact = kbucket.updateIfEqualMatchElseAddIfNotFull(testcontact);
	assertEquals("Failed to return null when adding known and matching contact", null, returnedcontact);

	expectedint = 2;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to not add an existing nodeid", expectedint, obtainedint);

	for (int i = 2; i < num; i++)
	{
	    randomUniqueId = UniqueId.newRandom();
	    secondcontact = new Contact(randomUniqueId, IP_ADDRESS1, i );
	    returnedcontact = kbucket.updateIfEqualMatchElseAddIfNotFull(secondcontact);
	    if (returnedcontact != null)
	    {
		returnedcontact.isLocked.set(false); //XXX should updateOrAddNew test
	    }
	}

	expectedint = DHT.K;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to not add when full", expectedint, obtainedint);
	assertEquals("Failed to return oldest when full", firstcontact, returnedcontact);
    }

    @Test 
    public void testReplaceIfKnown()
    {
	KBucket kbucket = new KBucket(0);
	Contact firstcontact;
	Contact secondcontact;
	boolean known;
	UniqueId randomUniqueId;
	int expectedint;
	int obtainedint;
	Contact testcontact;
	//ContactByDistance[] closest;
	Contact[] closest;

	randomUniqueId = UniqueId.newRandom();
	firstcontact = new Contact(randomUniqueId, IP_ADDRESS1, 0 );
	kbucket.updateIfEqualMatchElseAddIfNotFull(firstcontact);
	//System.out.println("\nfirstcontact : " + firstcontact);

	randomUniqueId = UniqueId.newRandom();
	secondcontact = new Contact(randomUniqueId, IP_ADDRESS2, 1 );
	kbucket.updateIfEqualMatchElseAddIfNotFull(secondcontact);
	//System.out.println("secondcontact: " + secondcontact);

	expectedint = 2;
	obtainedint = kbucket.getSize();
	assertEquals("Failed to add when not full", expectedint, obtainedint);

	randomUniqueId = UniqueId.newRandom();
	testcontact  = new Contact(randomUniqueId, IP_ADDRESS2, 2 );
	known = kbucket.replace(secondcontact, testcontact);
	//System.out.println("testcontact  : " + testcontact);
	assertEquals("Failed to return true when replaced known", true, known);

	expectedint = 2;
	obtainedint = kbucket.getSize();
	assertEquals("Failed size check", expectedint, obtainedint);

	//System.out.println("\nkbucket: " + kbucket + "\n");
	
	closest = kbucket.sortByDistanceTo(testcontact.uniqueId);
	//System.out.println("Closest:");
	//for (Contact c: closest)
	//{
	//    System.out.println(c);
	//}
	//System.out.println();
	assertEquals("Failed to perform replacement", testcontact, closest[0]);// Failing this test!

	randomUniqueId = UniqueId.newRandom();
	testcontact  = new Contact(randomUniqueId, IP_ADDRESS2, 3 );
	known = kbucket.replace(testcontact, testcontact);
	assertEquals("Failed to return false when did not replace", false, known);

	expectedint = 2;
	obtainedint = kbucket.getSize();
	assertEquals("Failed size check", expectedint, obtainedint);

	closest = kbucket.sortByDistanceTo(testcontact.uniqueId);
	assertEquals("Failed to not replace", false, closest[0].uniqueId.matches(testcontact.uniqueId));

    }

    @Test
    public void testSortByDistanceTo()
    {
	final int num = DHT.K * 2;
	KBucket kbucket = new KBucket(0);
	Contact firstcontact;
	Contact secondcontact;
	boolean known;
	UniqueId randomUniqueId;
	int expectedint;
	int obtainedint;
	Contact expectedcontact;
	Contact obtainedcontact;
	Contact[] closest;
	Contact[] contacts = new Contact[20];

	contacts[0]  = new Contact(new UniqueId("00000"), IP_ADDRESS1, 0);
	contacts[1]  = new Contact(new UniqueId("00001"), IP_ADDRESS2, 1);
	contacts[2]  = new Contact(new UniqueId("00010"), IP_ADDRESS2, 2);
	contacts[3]  = new Contact(new UniqueId("00011"), IP_ADDRESS2, 3);
	contacts[4]  = new Contact(new UniqueId("00100"), IP_ADDRESS2, 4);
	contacts[5]  = new Contact(new UniqueId("00101"), IP_ADDRESS2, 5);
	contacts[6]  = new Contact(new UniqueId("00110"), IP_ADDRESS2, 6);
	contacts[7]  = new Contact(new UniqueId("00111"), IP_ADDRESS2, 7);
	contacts[8]  = new Contact(new UniqueId("01000"), IP_ADDRESS2, 8);
	contacts[9]  = new Contact(new UniqueId("01001"), IP_ADDRESS2, 9);
	contacts[10] = new Contact(new UniqueId("01010"), IP_ADDRESS2, 10);
	contacts[11] = new Contact(new UniqueId("01011"), IP_ADDRESS2, 11);
	contacts[12] = new Contact(new UniqueId("01100"), IP_ADDRESS2, 12);
	contacts[13] = new Contact(new UniqueId("01101"), IP_ADDRESS2, 13);
	contacts[14] = new Contact(new UniqueId("01110"), IP_ADDRESS2, 14);
	contacts[15] = new Contact(new UniqueId("01111"), IP_ADDRESS2, 15);
	contacts[16] = new Contact(new UniqueId("10000"), IP_ADDRESS2, 16);
	contacts[17] = new Contact(new UniqueId("10001"), IP_ADDRESS2, 17);
	contacts[18] = new Contact(new UniqueId("10010"), IP_ADDRESS2, 18);
	contacts[19] = new Contact(new UniqueId("10011"), IP_ADDRESS2, 19);
	for (int i = 0; i < 20; i++)
	{
	    kbucket.updateIfEqualMatchElseAddIfNotFull(contacts[i]);
	}
	expectedint = 20;
	obtainedint = kbucket.getSize();
	assertEquals("Failed size check", expectedint, obtainedint);

	
	
	closest = kbucket.sortByDistanceTo(contacts[0].uniqueId);
	//for (Contact c: closest)
	//{
	//    System.out.println(c);
	//}
	//System.out.println();
	expectedcontact = contacts[0];
	obtainedcontact = closest[0];
	assertEquals("Failed to get closest", expectedcontact, obtainedcontact);

	expectedcontact = contacts[1];
	obtainedcontact = closest[1];
	assertEquals("Failed to get 2nd closest", expectedcontact, obtainedcontact);

	expectedcontact = contacts[19];
	obtainedcontact = closest[19];
	assertEquals("Failed to get farthest", expectedcontact, obtainedcontact);

	

	closest = kbucket.sortByDistanceTo(contacts[19].uniqueId);
	//for (Contact c: closest)
	//{
	 //  System.out.println(c);
	//}
	expectedcontact = contacts[19];
	obtainedcontact = closest[0];
	assertEquals("Failed to get closest", expectedcontact, obtainedcontact);

	expectedcontact = contacts[12]; // unintuitive, but remember (19) 10011 ^ (12) 01100 = 11111 (largest distance)
	obtainedcontact = closest[19];
	assertEquals("Failed to get farthest", expectedcontact, obtainedcontact);
    }

    public static void main(String[] args)
    {
	org.junit.runner.JUnitCore.main(TestKBucket.class.getName());
    }

    @Override
    public boolean isConnected(Contact oldest)
    {
	throw new UnsupportedOperationException("Not supported yet.");
    }
}